﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Diagnostics;
using System.Linq;

namespace PhotoManage;

public struct ExifTagItem {
    public string group;
    public string name;
    public string value;
}

public class ExifToolWrapper : List<ExifTagItem> {
    #region public methods
    public ExifTagItem Find(string tagname) {
        var q_items = from tagItem in this
                      where tagItem.name == tagname
                      select tagItem;
        return q_items.First();
    }
    #endregion
    #region Private methods


    #endregion
}
#region 常驻模式相关函数
public sealed class ExifTools : IDisposable {
    private static readonly ExifTools _instance = new();
    private ExifTools() { }
    public static ExifTools GetInstance() {
        return _instance;
    }
    string[] globalArgs = {
        "fast",
        "m",
        "S",
    };
    bool ExifToolHasBeenLoaded = false;
    Process pExifTool;

    public string Run(string cmd) {
        string stdOut = null;
        string stdErr = null;
        // exiftool command
        string program = "\"%COMSPEC%\"";
        Process tpExifTool = new() {
            StartInfo = new ProcessStartInfo(
            Environment.ExpandEnvironmentVariables(program),
            string.Format("/c \"{0}\"", @cmd)) {
                RedirectStandardOutput = true,
                //  NOTE:  If you do not implement an asynchronous error handler like in this example, instead simply using pExifTool.StandardError.ReadLine()
                //         in the following, you risk that your program might stall.  This is because ExifTool sometimes reports failure only through a
                //         StandardOutput line saying something like "0 output files created" without reporting anything in addition via StandardError, so
                //         pExifTool.StandardError.ReadLine() would wait indefinitely for an error message that never comes.
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            }
        };

        Thread thread_ReadStandardError = new(new ThreadStart(() => {
            if (tpExifTool != null) {
                //...  do something with the information provided in errLine.Data...
                stdErr = tpExifTool.StandardError.ReadToEnd();
            }
        }));
        Thread thread_ReadStandardOut = new(new ThreadStart(() => {
            if (tpExifTool != null) {
                stdOut = tpExifTool.StandardOutput.ReadToEnd();
            }
        }));

        tpExifTool.Start();
        thread_ReadStandardError.Start();
        thread_ReadStandardOut.Start();

        tpExifTool.WaitForExit();

        thread_ReadStandardError.Join();
        thread_ReadStandardOut.Join();

        string output = stdOut + stdErr;
        tpExifTool.Close();
        tpExifTool.Kill();
        return output;
    }
    public void RunExiftoolWhileStayingOpen() {
        //  Start ExifTool and keep it in memory if that has not been done yet.
        if (!ExifToolHasBeenLoaded) {
            pExifTool = new();
            File.WriteAllText(GetAppPath() + "\\args.txt", string.Empty);
            // exiftool command
            string command = "\"" + GetAppPath() + "\\exiftool.exe\" -stay_open true -@ args.txt";

            string program = "\"%COMSPEC%\"";
            pExifTool.StartInfo = new ProcessStartInfo(
                Environment.ExpandEnvironmentVariables(program),
                string.Format("/c {0}", @command)) {
                RedirectStandardOutput = true,
                //  NOTE:  If you do not implement an asynchronous error handler like in this example, instead simply using pExifTool.StandardError.ReadLine()
                //         in the following, you risk that your program might stall.  This is because ExifTool sometimes reports failure only through a
                //         StandardOutput line saying something like "0 output files created" without reporting anything in addition via StandardError, so
                //         pExifTool.StandardError.ReadLine() would wait indefinitely for an error message that never comes.
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };
            pExifTool.ErrorDataReceived += new DataReceivedEventHandler(ReadStandardErrorEvent);
            pExifTool.OutputDataReceived += new DataReceivedEventHandler(ReadStandardOutEvent);

            pExifTool.Start();
            pExifTool.BeginOutputReadLine();
            ////  This command starts the error handling, meaning ETErrorHandler() will now be called whenever ExifTool reports an error.
            pExifTool.BeginErrorReadLine();

            ExifToolHasBeenLoaded = true;
        }
    }

    public string GetFilesExifData(string[] files) {
        //  Append the args file for Exiftool to start reading and executing the command.
        //  NOTE:  NEVER use WriteAllLines here - ExifTool expects the args file to be appended continually, not re-written.

        //  This tells ExifTool to read out all of the image's metadata.
        File.AppendAllLines(GetAppPath() + "\\args.txt", files);  // 
        File.AppendAllLines(GetAppPath() + "\\args.txt", new string[] { "-S", "-m" });  // 
        //var args = files.Append("-execute");
        File.AppendAllLines(GetAppPath() + "\\args.txt", ExifData.AllTags.Select(x => "-" + x).ToArray());  //  
        File.AppendAllText(GetAppPath() + "\\args.txt", "-execute");  //  args.txt gets written into the folder where exiftool.exe resides here.

        string line = string.Empty;
        //do {
        //    line = pExifTool.StandardOutput.ReadLine();

        //    //  NOTE:  Depending on the command you issued, line will either contain a progress report of an operation (e.g., "1 output files created"),
        //    //         give line-by-line data, such as an image's metadata (e.g. "Orientation                     : Horizontal (normal)"), or
        //    //         read "{ready}", which indicates that executing the last command in args.txt has completed.

        //    //...  do something with the information provided in line...
        //} while (!line.Contains("{ready}"));
        return line;
    }
    public bool CheckToolExists() {
        string toolPath = GetAppPath();
        toolPath += "/exiftool.exe -ver";

        string output = "";
        if (!File.Exists("ExifTool.exe")) {
            return false;
        }
        else {
            try {
                output = Run(toolPath);
            }
            catch (Exception) {
            }
        }
        return output.Length >= 4;
    }
    /// <summary>
    /// Gets the path from where the executable is being run
    /// </summary>
    /// <returns>Path</returns> 
    private string GetAppPath() {
        string AppPath;
        AppPath = Assembly.GetExecutingAssembly().Location;
        AppPath = Path.GetDirectoryName(AppPath);
        return AppPath;
    }
    //  Finally, this is the asynchronous error handler
    private void ReadStandardErrorEvent(object sendingProcess, DataReceivedEventArgs args) {
        if (pExifTool != null) {
            var output = args.Data;
            if (string.IsNullOrEmpty(output)) {
                return;
            }
        }
    }

    private void ReadStandardOutEvent(object sendingProcess, DataReceivedEventArgs args) {
        if (pExifTool != null) {
            //var output = pExifTool.StandardOutput.ReadToEnd();
            var output = args.Data;
            if (string.IsNullOrEmpty(output)) {
                return;
            }
            Debug.WriteLine(output);
            while (output.Length > 0) {
                int epos = output.IndexOf('\r');

                if (epos < 0)
                    epos = output.Length;
                string tmp = output.Substring(0, epos);
                int tpos1 = tmp.IndexOf('\t');
                int tpos2 = tmp.IndexOf('\t', tpos1 + 1);

                if (tpos1 > 0 && tpos2 > 0) {
                    string taggroup = tmp.Substring(0, tpos1);
                    ++tpos1;
                    string tagname = tmp.Substring(tpos1, tpos2 - tpos1);
                    ++tpos2;
                    string tagvalue = tmp.Substring(tpos2, tmp.Length - tpos2);

                    // special processing for tags with binary data 
                    tpos1 = tagvalue.IndexOf(", use -b option to extract");
                    if (tpos1 >= 0)
                        tagvalue.Remove(tpos1, 26);

                    ExifTagItem itm;
                    itm.name = tagname;
                    itm.value = tagvalue;
                    itm.group = taggroup;
                    //this.Add(itm);
                }

                // is \r followed by \n ?
                if (epos < output.Length)
                    epos += (output[epos + 1] == '\n') ? 2 : 1;
                output = output.Substring(epos, output.Length - epos);
            }
        }
    }
    public void Dispose() {
        if (ExifToolHasBeenLoaded) {
            pExifTool.Close();
            pExifTool.Kill();
        }
    }
}
#endregion